## Generales
library(dplyr)   # Limpieza, filtrado y modificación de datos
library(ggplot2) # Visualización de datos
library(stringr) # Operaciones básicas de limpieza de texto
library(lubridate) # Operaciones básicas para manejar variables en formato date

#Datos geograficos
library(sf)      #Manipulacion de datos espaciales. Hay muchos, este es el mas recomendado
library(tmap)    #visualización de datos espaciales. Same as  above

# Text as adata
library(rtweet) # Funciones para conectarnos a la API de twitter
library(tm)     # Funciones avanzadas de text-mining 
library(SnowballC) # Funciones para encontrar la raíz de cada palabra
library(wordcloud) # Funciones para visualizar nubes de palabras
library(syuzhet)   # Funciones para analisis de sentimiento

# Text as data/Networks
library(igraph)
library(ggraph)

Everything is data

Algunos argumentan que “todo es data”. Nosotros hacemos la salvedad de que para que algo sea considerado “data apta para el análisis estadístico” debe ser medible y cuatificable de forma estructurada.

Hay muchas cosas que cumplen estos requisitos.

Audio is data. Científicos de la universidad de Harvard crean algoritmo de ML para adivinar cuales temas de Los Beatels escribió Paul McCartney. Source: “Lennon or McCartney? Machine learning tries to crack disputed Beatles authorship”, Financial Times

La revolución del Big Data

Las tecnologías digitales impulsan nuestra capacidad de generar, almancenar, y procesar datos.

El la proliferación del big-data nos brinda muchas posibilidades

Nuevas fuentes de datos nos brindan muchas posibilidades. Source: Rowe, F. 2021. Big Data and Human Geography. In: Demeritt, D. and Lees L. (eds) ConciseEncyclopedia of Human Geography. Edward Elgar Encyclopedias in the Social Sciences series.

Investigadores de múltiples disciplinas están usando algoriímos de IA para digitalizar textos antiguos (mucho mejor que escanear y transcribir). Source: Zejiang Shen, Kaixuan Zhang, Melissa Dell; Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR) Workshops, 2020, pp. 548-549

La capacidad de procesamiento de nuestras computadores ha incrementado sustancialmente. Se ha multiplicado por 1 billón en los últimos 50 años. Source: Visualizing the Trillion-Fold Increase in Computing Power. [Visualcapitalist.com](https://www.visualcapitalist.com)

¿Por qué nos interesa?

Muchos ejercicios de inferencia causal y de machine learning pueden llegar a mejor puerto con más información.

Un ejemplo

Hablémos brevemente del trabajo de Bazzi, Fisbein y Bebresilasse (2020).

bazzi

Bazzi, S., M. Fiszbein, & M. Gebresilasse [2020]; “Frontier Culture: The Roots and Persistence of ‘Rugged Individualism’ in The United States,” Econometrica.

bazzi

Bazzi, S., M. Fiszbein, & M. Gebresilasse [2020]; “Frontier Culture: The Roots and Persistence of ‘Rugged Individualism’ in The United States,” Econometrica.

bazzi

Bazzi, S., M. Fiszbein, & M. Gebresilasse [2020]; “Frontier Culture: The Roots and Persistence of ‘Rugged Individualism’ in The United States,” Econometrica.

Mapas

Haremos un tour introductorio sobre algunos usos de los datos geográficos para producir evidencia.

Tipos de datos geográficos

Hay varios tipos de datos espaciales. Conocerlos y tener una noción de su estructura es clave para definir su uso. En resumen, hay tres tipos de objetos (puntos, líneas y áreas) y dos modelos de datos (vector y raster).

Source: Kolak ,M & Lin, Q. SER Workshop 2022 - “Intro to Spatial Analysis & GIS for Spatial Epidemiology in R”

¿En qué formatos podemos encontralos?

  • Un simple CSV (o un .xslx) puede almacenar informacón de latitud y la longitud. Eso clasifica cómo datos geográficos.

  • Shapefiles: Es un formato de datos especial patentado por ESRI que recopila información de 4 archivos con extensiones diferentes (.shp, .shx, .dbf, and .prj)

  • GeoJSON: Un formato de datos abiertos lo suficientemente versátil como para adaptarse a varias estructuras de datos geográficos.

¿Qué son los Coordinate Reference Systems (CRS)?

Los mapas son una proyección en dos dimensiones de datos tridimensionales. En el proceso de proyección se distorsiona el tamaño relativo de los puntos en el mapa (esta app ilustra muy bien lo que sucede). Cada método de proyección esta asociado a una CRS diferente.

Source: Kolak ,M & Lin, Q. SER Workshop 2022 - “Intro to Spatial Analysis & GIS for Spatial Epidemiology in R”

Acceso a la salud pública en chicago

Replicaremos algunos elementos del Opioid Environment Toolkit, desarrollado por Healthy Regions & Policies Lab.

Imagina que estás en tu nueva pasantía en el ministerio de Salud de Chicago y te piden un análisis de la accesibilidad a Centros de Atención de Opiodes. Todo lo que tienes son los datos listados abajo y consola de Rstudio:

  • Clínicas de métadona de Chicago (methadone_clinics.shp): Ubicación de clíncas para dispensar medicamentos usados en el tratamiento de dependencia de opioides.

  • Códigos postales de Chicago (chicago_zips.shp): Polígonos delineando los límites de cada código postal en Chicago.

  • Límites de Chicago (boundaries_chicago.geojson): Polígonos delineando la frontera de Chicago.

Leyendo datos espaciales

Con el paquete sf es bastante sencillo.

metClinics <- st_read("data/opioids_toolkit_data/methadone_clinics.shp") # disclaimer: mira la definicion de los shapefile que pusimos arriba. Necesariamente deben haber 4 archivos con el mismo nombre pero difernente terminacion en esa carpeta
## Reading layer `methadone_clinics' from data source 
##   `C:\Users\cdabo\Dropbox\Causal Inference 2022\Documents\Workshop_01\data\opioids_toolkit_data\methadone_clinics.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 27 features and 8 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: -87.7349 ymin: 41.68698 xmax: -87.57673 ymax: 41.96475
## Geodetic CRS:  WGS 84
chicagoZips <- st_read("data/opioids_toolkit_data/chicago_zips.shp")
## Reading layer `chicago_zips' from data source 
##   `C:\Users\cdabo\Dropbox\Causal Inference 2022\Documents\Workshop_01\data\opioids_toolkit_data\chicago_zips.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 85 features and 9 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -88.06058 ymin: 41.58152 xmax: -87.52366 ymax: 42.06504
## Geodetic CRS:  WGS 84
cityBoundary <- st_read("data/opioids_toolkit_data/boundaries_chicago.geojson")
## Reading layer `boundaries_chicago' from data source 
##   `C:\Users\cdabo\Dropbox\Causal Inference 2022\Documents\Workshop_01\data\opioids_toolkit_data\boundaries_chicago.geojson' 
##   using driver `GeoJSON'
## Simple feature collection with 1 feature and 4 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -87.94011 ymin: 41.64454 xmax: -87.52414 ymax: 42.02304
## Geodetic CRS:  WGS 84

Nuestros primeros mapas ¿Hay que hacer ajustes?

tmap_mode("plot") #esta funcion garantiza que tmap solo produzca graficos estaticos (son mas livianos)

#Tmap funciona casi igual que ggplot. Cada detalle se va añadiendo como capas adicionales despues de un simbolo +

## 1era capa: Poligonos, areas
tm_shape(chicagoZips) + 
  tm_borders(alpha = 0.4) + 
  ## 2da capa: Puntos
  tm_shape(metClinics) + 
  tm_dots(size = 0.4, col="red") +
  tm_layout(title="¿Cómo saber si está bien?")

## Revisemos si ambos archivos shape tienen las mismas CRS
identical(st_crs(metClinics),st_crs(chicagoZips))
## [1] TRUE
## Estaba bien en el sentido de que estaban en el mismo CRS, pero hay una mejor alternativa.
## Usemos un CRS util para nuestro caso: Queremos uno que preserve las distancias. Buscamos en google "CRS Illinios" y encontramos el CRS EPSG:1748, la mas precisa para medir distancias en el estado de Illinois.

CRS.new  <- st_crs("EPSG:3435")

metClinics.3435 <- st_transform(metClinics, CRS.new)
chicagoZips.3435 <- st_transform(chicagoZips, CRS.new)

st_crs(metClinics.3435)
## Coordinate Reference System:
##   User input: EPSG:3435 
##   wkt:
## PROJCRS["NAD83 / Illinois East (ftUS)",
##     BASEGEOGCRS["NAD83",
##         DATUM["North American Datum 1983",
##             ELLIPSOID["GRS 1980",6378137,298.257222101,
##                 LENGTHUNIT["metre",1]]],
##         PRIMEM["Greenwich",0,
##             ANGLEUNIT["degree",0.0174532925199433]],
##         ID["EPSG",4269]],
##     CONVERSION["SPCS83 Illinois East zone (US Survey feet)",
##         METHOD["Transverse Mercator",
##             ID["EPSG",9807]],
##         PARAMETER["Latitude of natural origin",36.6666666666667,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8801]],
##         PARAMETER["Longitude of natural origin",-88.3333333333333,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8802]],
##         PARAMETER["Scale factor at natural origin",0.999975,
##             SCALEUNIT["unity",1],
##             ID["EPSG",8805]],
##         PARAMETER["False easting",984250,
##             LENGTHUNIT["US survey foot",0.304800609601219],
##             ID["EPSG",8806]],
##         PARAMETER["False northing",0,
##             LENGTHUNIT["US survey foot",0.304800609601219],
##             ID["EPSG",8807]]],
##     CS[Cartesian,2],
##         AXIS["easting (X)",east,
##             ORDER[1],
##             LENGTHUNIT["US survey foot",0.304800609601219]],
##         AXIS["northing (Y)",north,
##             ORDER[2],
##             LENGTHUNIT["US survey foot",0.304800609601219]],
##     USAGE[
##         SCOPE["Engineering survey, topographic mapping."],
##         AREA["United States (USA) - Illinois - counties of Boone; Champaign; Clark; Clay; Coles; Cook; Crawford; Cumberland; De Kalb; De Witt; Douglas; Du Page; Edgar; Edwards; Effingham; Fayette; Ford; Franklin; Gallatin; Grundy; Hamilton; Hardin; Iroquois; Jasper; Jefferson; Johnson; Kane; Kankakee; Kendall; La Salle; Lake; Lawrence; Livingston; Macon; Marion; Massac; McHenry; McLean; Moultrie; Piatt; Pope; Richland; Saline; Shelby; Vermilion; Wabash; Wayne; White; Will; Williamson."],
##         BBOX[37.06,-89.28,42.5,-87.02]],
##     ID["EPSG",3435]]

Identifiquemos regiones de acceso inmediato

Dibujaremos una circunferencia de 1 milla de radio al rededor de cada clínica.

# 1 mella son 5280 pies
metClinic_buffers <- st_buffer(metClinics.3435, dist = 5280)

# Primer intento
tm_shape(chicagoZips.3435) + tm_borders() +
  tm_shape(metClinics.3435) + tm_dots(col = "red") +
  # añadimos los buffers (circunferencias)
  tm_shape(metClinic_buffers) + tm_borders(col = "blue")

# Segundo intento
tm_shape(chicagoZips) + tm_borders(alpha = 0.6) +
  # ponemos los buffers antes de los bordes y los puntos, y los rellenamos
  tm_shape(metClinic_buffers) + tm_fill(col = "blue", alpha = .4) +
  tm_borders(col = "blue") +
  tm_shape(metClinics.3435) + tm_dots(col = "red",  size = 0.2) 

Hay varias circunferencias que se superponen, unámoslas en áreas comunes de acceso immediato

# Union: junto todas las circunferencias que se superpongan. Tambien hay intersección (util para identificar areas cubiertas por mas de una clinica)
unionBuffers <- st_union(metClinic_buffers)

tm_shape(chicagoZips) +  tm_borders()+
  # No añades el shape de las circunferencias, sino de las nuevas areas comunes
  tm_shape(unionBuffers) + tm_fill(col = "blue", alpha = .2) +
  tm_borders(col = "blue") +
  tm_shape(metClinics.3435) + tm_dots(col = "red", size = 0.4) 

¿Qué tan lejos están los centros de atención para las areas mas remotas?

Te felicitan por delinear las áreas comunes de atención immediata, pero pero al final de la reunión te preguntan qué tan lejos está el centro la clínica mas cercana para los habitantes del código postal 22145.

No tenemos la ubicación del habitante promedio del código postal 22145, pero, haciendo algunas suposiciones, podemos aproximarnos a ella y calcular la distancia entre esta ubicación y el centro de atención mas cercano. Usaremos el centroide de cada código postal cómo nuestra estimación.

chicagoCentroids <- st_centroid(chicagoZips) # esta funcion nos permite obtener los centroides

chicagoCentroids.3435 <- st_transform(chicagoCentroids, CRS.new)

#vistazo a la data
chicagoCentroids
## Simple feature collection with 85 features and 9 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: -87.99311 ymin: 41.61322 xmax: -87.55133 ymax: 42.03526
## Geodetic CRS:  WGS 84
## First 10 features:
##    ZCTA5CE10 GEOID10 CLASSFP10 MTFCC10 FUNCSTAT10  ALAND10 AWATER10  INTPTLAT10
## 1      60501   60501        B5   G6350          S 12532295   974360 +41.7802209
## 2      60007   60007        B5   G6350          S 36493383   917560 +42.0086000
## 3      60651   60651        B5   G6350          S  9052862        0 +41.9020934
## 4      60652   60652        B5   G6350          S 12987857        0 +41.7479319
## 5      60653   60653        B5   G6350          S  6041418  1696670 +41.8199645
## 6      60654   60654        B5   G6350          S  1464813   113471 +41.8918225
## 7      60655   60655        B5   G6350          S 11408010        0 +41.6947762
## 8      60656   60656        B5   G6350          S  8465226        0 +41.9742800
## 9      60657   60657        B5   G6350          S  5888324  2025836 +41.9402931
## 10     60659   60659        B5   G6350          S  5251086     2818 +41.9914885
##      INTPTLON10                   geometry
## 1  -087.8232440 POINT (-87.82396 41.78014)
## 2  -087.9973398 POINT (-87.99311 42.00759)
## 3  -087.7408565 POINT (-87.74086 41.90209)
## 4  -087.7147951  POINT (-87.7148 41.74793)
## 5  -087.6059654 POINT (-87.60604 41.81987)
## 6  -087.6383036 POINT (-87.63726 41.89228)
## 7  -087.7037764 POINT (-87.70378 41.69478)
## 8  -087.8271283 POINT (-87.82713 41.97428)
## 9  -087.6468569 POINT (-87.64926 41.94033)
## 10 -087.7039859 POINT (-87.70406 41.99115)
#vistazo a la data (grafico)
tm_shape(chicagoZips) +
  tm_borders() +
  tm_shape(chicagoCentroids.3435) +
  tm_dots()+
  tm_shape(metClinics.3435) +
  tm_dots(col = "red", size = 0.4) 

## Esta funcion calcula la distancia e identifica los pares mas cercanos
## Veamos que hay en ?st_nearest_feature()
nearestClinic_indexes <- st_nearest_feature(x= chicagoCentroids.3435, 
                                            y = metClinics.3435)


# nearestClinic_indexes es un vector Clinica mas cercano para cada zip code de chicago

# De todas las clinicas, conservamos la que aparecen en la lista de mas cercanas a algún codigo postal
nearestClinic <- metClinics.3435[nearestClinic_indexes,]

nearestClinic # la clinica mas cercana a cada codigo postal
## Simple feature collection with 85 features and 8 fields
## Geometry type: POINT
## Dimension:     XY
## Bounding box:  xmin: 1147259 ymin: 1829330 xmax: 1190680 ymax: 1930471
## Projected CRS: NAD83 / Illinois East (ftUS)
## First 10 features:
##       X                                                         Name
## 16   16                          Katherine Boone Robinson Foundation
## 7     7                                     A Rincon Family Services
## 7.1   7                                     A Rincon Family Services
## 26   26                            New Hope Community Service Center
## 15   15         HRDI- Grand Boulevard Professional Counseling Center
## 5     5                          Center for Addictive Problems, Inc.
## 26.1 26                            New Hope Community Service Center
## 7.2   7                                     A Rincon Family Services
## 1     1                Chicago Treatment and Counseling Center, Inc.
## 3     3 Soft Landing Interventions/DBA Symetria Recovery of Lakeview
##                      Address    City State   Zip
## 16        4100 W. Ogden Ave. Chicago    IL 60623
## 7         3809 W. Grand Ave. Chicago    IL 60651
## 7.1       3809 W. Grand Ave. Chicago    IL 60651
## 26          2559 W. 79th St. Chicago    IL 60652
## 15           340 E. 51st St. Chicago    IL 60615
## 5           609 N. Wells St. Chicago    IL 60654
## 26.1        2559 W. 79th St. Chicago    IL 60652
## 7.2       3809 W. Grand Ave. Chicago    IL 60651
## 1    4453 North Broadway st. Chicago    IL 60640
## 3       3934 N. Lincoln Ave. Chicago    IL 60613
##                                       fullAdd geo_method
## 16        4100 W. Ogden Ave. Chicago IL 60623     census
## 7         3809 W. Grand Ave. Chicago IL 60651     census
## 7.1       3809 W. Grand Ave. Chicago IL 60651     census
## 26          2559 W. 79th St. Chicago IL 60652        osm
## 15           340 E. 51st St. Chicago IL 60615     census
## 5           609 N. Wells St. Chicago IL 60654     census
## 26.1        2559 W. 79th St. Chicago IL 60652        osm
## 7.2       3809 W. Grand Ave. Chicago IL 60651     census
## 1    4453 North Broadway st. Chicago IL 60640        osm
## 3       3934 N. Lincoln Ave. Chicago IL 60613     census
##                     geometry
## 16   POINT (1149437 1888620)
## 7    POINT (1150707 1908328)
## 7.1  POINT (1150707 1908328)
## 26   POINT (1160422 1852071)
## 15   POINT (1179319 1871281)
## 5    POINT (1174632 1904257)
## 26.1 POINT (1160422 1852071)
## 7.2  POINT (1150707 1908328)
## 1    POINT (1168556 1929911)
## 3    POINT (1162460 1926257)
## Esta funcion nos permite ir un paso atras y recalcular la distancia entre ambos sets de puntos (los zip codes y sus clinicas mas cercanas)
min_dist<-st_distance(x = chicagoCentroids.3435,
                      y = nearestClinic, by_element = TRUE)

min_dist_mi <- min_dist/5280

hist(min_dist_mi)

Grafiquemos la distancia en el mapa

# Lo bueno de los objetos sf es que les puedes añadir columnas y modificar atributos cómo si de un data.frame se tratase
min_dist_mi <- cbind(chicagoZips, min_dist_mi)
min_dist_mi
## Simple feature collection with 85 features and 10 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -88.06058 ymin: 41.58152 xmax: -87.52366 ymax: 42.06504
## Geodetic CRS:  WGS 84
## First 10 features:
##    ZCTA5CE10 GEOID10 CLASSFP10 MTFCC10 FUNCSTAT10  ALAND10 AWATER10  INTPTLAT10
## 1      60501   60501        B5   G6350          S 12532295   974360 +41.7802209
## 2      60007   60007        B5   G6350          S 36493383   917560 +42.0086000
## 3      60651   60651        B5   G6350          S  9052862        0 +41.9020934
## 4      60652   60652        B5   G6350          S 12987857        0 +41.7479319
## 5      60653   60653        B5   G6350          S  6041418  1696670 +41.8199645
## 6      60654   60654        B5   G6350          S  1464813   113471 +41.8918225
## 7      60655   60655        B5   G6350          S 11408010        0 +41.6947762
## 8      60656   60656        B5   G6350          S  8465226        0 +41.9742800
## 9      60657   60657        B5   G6350          S  5888324  2025836 +41.9402931
## 10     60659   60659        B5   G6350          S  5251086     2818 +41.9914885
##      INTPTLON10                min_dist_mi                       geometry
## 1  -087.8232440  6.962975 [US_survey_foot] MULTIPOLYGON (((-87.86289 4...
## 2  -087.9973398 15.685945 [US_survey_foot] MULTIPOLYGON (((-88.06058 4...
## 3  -087.7408565  0.992006 [US_survey_foot] MULTIPOLYGON (((-87.77559 4...
## 4  -087.7147951  1.405126 [US_survey_foot] MULTIPOLYGON (((-87.74205 4...
## 5  -087.6059654  1.371441 [US_survey_foot] MULTIPOLYGON (((-87.62623 4...
## 6  -087.6383036  0.165529 [US_survey_foot] MULTIPOLYGON (((-87.64775 4...
## 7  -087.7037764  3.885578 [US_survey_foot] MULTIPOLYGON (((-87.73973 4...
## 8  -087.8271283  7.260946 [US_survey_foot] MULTIPOLYGON (((-87.86957 4...
## 9  -087.6468569  1.612944 [US_survey_foot] MULTIPOLYGON (((-87.6785 41...
## 10 -087.7039859  2.931816 [US_survey_foot] MULTIPOLYGON (((-87.7289 41...
tm_shape(min_dist_mi) +
  tm_polygons("min_dist_mi", style = 'quantile', n=5,
              title = "Minimum Distance (mi)") +
  tm_layout(main.title = "Minimum Distance from Zip Centroid\n to Methadone Clinic",
            main.title.position = "center",
            main.title.size = 1)

Eramos muchos, y llegó el COVID-19

Tu jefe te interrumpe mientras preparabas tu nuevo informe. Debido al repunte de casos de Covid-19, es urgente que añadas a tu informe cuales son las Clínicas de Métadonas con menos riesgo de contagio.

La cantidad de casos por cada 100 mil habitantes está en la data “COVID-

19_Cases__Tests__and_Deaths_by_ZIP_Code.csv” (disponible en el Portal de Datos de Chicago)

COVID <- read.csv("data/opioids_toolkit_data/COVID-19_Cases__Tests__and_Deaths_by_ZIP_Code.csv")

## Primero inspeccionemos el dataset de COVID
head(COVID)
##   ZIP.Code Week.Number Week.Start   Week.End Cases...Weekly Cases...Cumulative
## 1    60602          44 10/25/2020 10/31/2020              3                 35
## 2    60604          51 12/13/2020 12/19/2020              0                 74
## 3    60604          52 12/20/2020 12/26/2020              2                 76
## 4    60604          53 12/27/2020 01/02/2021              1                 77
## 5    60604          24 06/07/2020 06/13/2020              1                 22
## 6    60604          25 06/14/2020 06/20/2020              0                 22
##   Case.Rate...Weekly Case.Rate...Cumulative Tests...Weekly Tests...Cumulative
## 1                241                 2813.5             65                976
## 2                  0                 9462.9             75               1073
## 3                256                 9718.7             49               1122
## 4                128                 9846.5             54               1176
## 5                128                 2813.3              9                134
## 6                  0                 2813.3             16                150
##   Test.Rate...Weekly Test.Rate...Cumulative Percent.Tested.Positive...Weekly
## 1               5225                78456.6                              0.0
## 2               9591               137212.3                              0.0
## 3               6266               143478.3                              0.1
## 4               6905               150383.6                              0.0
## 5               1151                17135.5                              0.1
## 6               2046                19181.6                              0.0
##   Percent.Tested.Positive...Cumulative Deaths...Weekly Deaths...Cumulative
## 1                                  0.0               0                   0
## 2                                  0.1               0                   0
## 3                                  0.1               0                   0
## 4                                  0.1               0                   0
## 5                                  0.2               0                   0
## 6                                  0.2               0                   0
##   Death.Rate...Weekly Death.Rate...Cumulative Population        Row.ID
## 1                   0                       0       1244 60602-2020-44
## 2                   0                       0        782 60604-2020-51
## 3                   0                       0        782 60604-2020-52
## 4                   0                       0        782 60604-2020-53
## 5                   0                       0        782 60604-2020-24
## 6                   0                       0        782 60604-2020-25
##              ZIP.Code.Location
## 1 POINT (-87.628309 41.883136)
## 2 POINT (-87.629029 41.878153)
## 3 POINT (-87.629029 41.878153)
## 4 POINT (-87.629029 41.878153)
## 5 POINT (-87.629029 41.878153)
## 6 POINT (-87.629029 41.878153)
names(COVID)
##  [1] "ZIP.Code"                            
##  [2] "Week.Number"                         
##  [3] "Week.Start"                          
##  [4] "Week.End"                            
##  [5] "Cases...Weekly"                      
##  [6] "Cases...Cumulative"                  
##  [7] "Case.Rate...Weekly"                  
##  [8] "Case.Rate...Cumulative"              
##  [9] "Tests...Weekly"                      
## [10] "Tests...Cumulative"                  
## [11] "Test.Rate...Weekly"                  
## [12] "Test.Rate...Cumulative"              
## [13] "Percent.Tested.Positive...Weekly"    
## [14] "Percent.Tested.Positive...Cumulative"
## [15] "Deaths...Weekly"                     
## [16] "Deaths...Cumulative"                 
## [17] "Death.Rate...Weekly"                 
## [18] "Death.Rate...Cumulative"             
## [19] "Population"                          
## [20] "Row.ID"                              
## [21] "ZIP.Code.Location"
## Tiene nombres muy raros, usemos la funcion clean_names() del paquete janitor
COVID<-janitor::clean_names(COVID)
names(COVID)
##  [1] "zip_code"                           "week_number"                       
##  [3] "week_start"                         "week_end"                          
##  [5] "cases_weekly"                       "cases_cumulative"                  
##  [7] "case_rate_weekly"                   "case_rate_cumulative"              
##  [9] "tests_weekly"                       "tests_cumulative"                  
## [11] "test_rate_weekly"                   "test_rate_cumulative"              
## [13] "percent_tested_positive_weekly"     "percent_tested_positive_cumulative"
## [15] "deaths_weekly"                      "deaths_cumulative"                 
## [17] "death_rate_weekly"                  "death_rate_cumulative"             
## [19] "population"                         "row_id"                            
## [21] "zip_code_location"
## Cuantas observaciones tiene? Hmmmm Parece que es un panel de datos. Conservemos el último período
nrow(COVID)
## [1] 7380
COVID$week_end<-str_replace_all(COVID$week_end,"/","-")
COVID$week_end<-lubridate::mdy(COVID$week_end)
COVID<-filter(COVID, week_end==max(week_end)) %>% 
  select(zip_code,week_end,case_rate_cumulative, death_rate_cumulative)

## Juntemos la base de datos con los zip codes de Chicago. El formato SF hace que sea tan sencillo como juntar dos data.frames
chicagoZips_covid<-chicagoZips %>% 
  ## A todos los zip codes de chicago le anexo su tasa acumulada de casos en la ultima semana de 2020
  inner_join(COVID, by=c("GEOID10"="zip_code"))


## Grafico heatmap con tasa de contagios.
tm_shape(chicagoZips_covid) +
  tm_fill("case_rate_cumulative", style="quantile", pal="-RdYlGn",
              title = "COVID Case Rate") +
  tm_shape(unionBuffers) + tm_fill(alpha=0.5) + 
  tm_borders(col = "grey") +
  tm_shape(metClinics.3435) + 
  tm_dots(col = "black", size = 0.1) +
  tm_layout(main.title="Clínicas de Metadona seguras",
            main.title.position = "center",
            main.title.size = 1,
            frame = FALSE)

Datos en formato Raster

El formato raster es distinto al formato shape (o vectores). Un archivo raster es una gran matriz donde cada pixel o grilla representa un area geográfica determinada. Heremos un breve ejercicio con los datos sobre la composición de la superficie de la tierra, que vienen incluídos en el paquete tm_map.

data(World, land)

## Inspeccionemos land y world
land$cover[1:5,1:5]
##      [,1]         [,2]         [,3]         [,4]         [,5]        
## [1,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [2,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [3,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [4,] Water bodies Water bodies Water bodies Water bodies Water bodies
## [5,] Water bodies Water bodies Water bodies Water bodies Water bodies
## 20 Levels: Broadleaf Evergreen Forest ... Water bodies
table(land$cover)  # uno de sus componenetes es una matriz donde cada celda muestra su tipo de superficie 
## 
##           Broadleaf Evergreen Forest           Broadleaf Deciduous Forest 
##                                 9140                                 6660 
##          Needleleaf Evergreen Forest          Needleleaf Deciduous Forest 
##                                 6124                                 6622 
##                         Mixed Forest                            Tree Open 
##                                 4134                                16171 
##                                Shrub                           Herbaceous 
##                                 9341                                21377 
##    Herbaceous with Sparse Tree/Shrub                    Sparse vegetation 
##                                 1893                                12247 
##                             Cropland                          Paddy field 
##                                11658                                  598 
##   Cropland / Other Vegetation Mosaic                             Mangrove 
##                                 5587                                   65 
##                              Wetland Bare area,consolidated (gravel,rock) 
##                                 1492                                 7436 
##      Bare area,unconsolidated (sand)                                Urban 
##                                 7221                                  388 
##                           Snow / Ice                         Water bodies 
##                                61986                               393060
land$trees[500:505,100:105]      # Tambien tiene otra matriz con el % de la celda cubierto por arboles
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]   NA   NA   NA   NA   NA   NA
## [2,]   NA   NA   NA   NA   NA   NA
## [3,]   NA   NA   NA   NA   NA   NA
## [4,]   NA   NA   NA   NA   NA   NA
## [5,]   NA   NA   NA   NA   NA   NA
## [6,]   NA   NA   NA   NA   NA   NA
table(land$trees, useNA = "always")
## 
##      0      1      2      3      4      5      6      7      8      9     10 
## 106841   3899   3374   3067   2926   2357   2310   1926   1898   1666   1631 
##     11     12     13     14     15     16     17     18     19     20     21 
##   1413   1401   1272   1262   1124   1168   1031   1094    985   1030    984 
##     22     23     24     25     26     27     28     29     30     31     32 
##    930    879    925    815    893    772    774    715    782    726    747 
##     33     34     35     36     37     38     39     40     41     42     43 
##    594    709    615    690    588    653    612    601    554    600    556 
##     44     45     46     47     48     49     50     51     52     53     54 
##    555    536    573    510    560    505    588    519    576    501    484 
##     55     56     57     58     59     60     61     62     63     64     65 
##    495    539    508    540    505    536    486    525    511    497    480 
##     66     67     68     69     70     71     72     73     74     75     76 
##    473    496    518    510    541    460    512    486    531    520    540 
##     77     78     79     80     81     82     83     84     85     86     87 
##    480    544    501    473    522    505    498    506    468    494    524 
##     88     89     90     91     92     93     94     95     96     97     98 
##    508    470    511    460    431    516    443    398    424    367    292 
##     99    100   <NA> 
##    298   2002 393060
land$elevation[100:104,100:105]          # Por ultimo esta la elevacion de cada celda respecto al nivel del mar
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]   NA   NA   NA   NA   NA   NA
## [2,]   NA   NA   NA   NA   NA   NA
## [3,]   NA   NA   NA   NA   NA   NA
## [4,]   NA   NA   NA   NA   NA   NA
## [5,]   NA   NA   NA   NA   NA   NA
## Visualicemos la densidad de los arboles plantados
tm_shape(land, ylim = c(-88,88)) +
  ## esta es la funcion para graficar los arboles
  tm_raster("trees", title = "Densidad de arboles plantados",palette = "Greens")+
  ## Poligonos de los continetnes y paises
  tm_shape(World) +
  ## Detalles esteticos
  tm_borders(col = "black") +
  tm_layout(scale = .8, 
    legend.position = c("left","bottom"),
    legend.bg.color = "white", legend.bg.alpha = .2, 
    legend.frame = "gray50")

## Primero debemos asegurarnos que LAND tenga las mismas coordenadas de World
CRS.new <- sf::st_crs(land)

World_new<-st_transform(World, CRS.new)

st_crs(World_new)
## Coordinate Reference System:
##   User input: +proj=longlat +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +no_defs 
##   wkt:
## BOUNDCRS[
##     SOURCECRS[
##         GEOGCRS["unknown",
##             DATUM["Unknown based on WGS84 ellipsoid",
##                 ELLIPSOID["WGS 84",6378137,298.257223563,
##                     LENGTHUNIT["metre",1],
##                     ID["EPSG",7030]]],
##             PRIMEM["Greenwich",0,
##                 ANGLEUNIT["degree",0.0174532925199433],
##                 ID["EPSG",8901]],
##             CS[ellipsoidal,2],
##                 AXIS["longitude",east,
##                     ORDER[1],
##                     ANGLEUNIT["degree",0.0174532925199433,
##                         ID["EPSG",9122]]],
##                 AXIS["latitude",north,
##                     ORDER[2],
##                     ANGLEUNIT["degree",0.0174532925199433,
##                         ID["EPSG",9122]]]]],
##     TARGETCRS[
##         GEOGCRS["WGS 84",
##             DATUM["World Geodetic System 1984",
##                 ELLIPSOID["WGS 84",6378137,298.257223563,
##                     LENGTHUNIT["metre",1]]],
##             PRIMEM["Greenwich",0,
##                 ANGLEUNIT["degree",0.0174532925199433]],
##             CS[ellipsoidal,2],
##                 AXIS["latitude",north,
##                     ORDER[1],
##                     ANGLEUNIT["degree",0.0174532925199433]],
##                 AXIS["longitude",east,
##                     ORDER[2],
##                     ANGLEUNIT["degree",0.0174532925199433]],
##             ID["EPSG",4326]]],
##     ABRIDGEDTRANSFORMATION["Transformation from unknown to WGS84",
##         METHOD["Position Vector transformation (geog2D domain)",
##             ID["EPSG",9606]],
##         PARAMETER["X-axis translation",0,
##             ID["EPSG",8605]],
##         PARAMETER["Y-axis translation",0,
##             ID["EPSG",8606]],
##         PARAMETER["Z-axis translation",0,
##             ID["EPSG",8607]],
##         PARAMETER["X-axis rotation",0,
##             ID["EPSG",8608]],
##         PARAMETER["Y-axis rotation",0,
##             ID["EPSG",8609]],
##         PARAMETER["Z-axis rotation",0,
##             ID["EPSG",8610]],
##         PARAMETER["Scale difference",1,
##             ID["EPSG",8611]]]]
## Usamos la funcion aggregate del paquete sf que projectara las celdas del raster en los poligonos de World (Puede tomar de 20 segundos a varios minutos dependiendo de tu PC)
land_country <- aggregate(land, World_new, mean, na.rm=T)

nrow(land_country)
## geometry 
##      177
nrow(World_new)
## [1] 177
World_trees <- mutate(World_new, trees = land_country$trees)


## Visualizémoslo en un ranking con ggplot, organizado por tipo de economia
World_trees  %>% 
  # convierto el sf object en un data.frame. En terminos practicos no cambia nada, pero es mas "limpio"
  as.data.frame()%>% 
  # Conservo las variables que quiero usar
  select(name, trees,continent) %>% 
  # No toods los continentes tienen arboles. Alquien me explica como funciona este comando?
  filter(! continent %in% c("Seven seas (open ocean)")) %>%
  filter(! name %in% c("Antarctica")) %>%
  # Agrupo por continente y preservo los top x valores
  group_by(continent) %>% 
  top_n(6, trees) %>% 
  # Inicio ggplot.
  ggplot(aes(x=reorder(name,trees), y=trees))+
  geom_col()+
  coord_flip()+
  facet_wrap(.~str_wrap(continent,50), ncol = 3,scales = "free")+
  labs(y="% de superficie con Árboles",
       x=NULL)

## Visualizémoslo en el mapa
tm_shape(World_trees)+
   tm_polygons("trees", style = 'quantile', n=10,palette="Greens",
              title = "% de superficie con Árboles") +
    tm_borders(col = "black")+
    tm_layout(frame = FALSE)

Texto

Un gran porcentaje de los datos producidos por el hombre está en formato de texto. Esta introducción tendrá un formato similar al anterior:

Conectémonos a la API de twitter

Haremos un ejercicio de análisis texto con twitter.

Referencias:

  • Cómo acceser a los datos de Twitter (link)

  • Todos pueden pedir permiso para crear una aplicación (acceder a la API) en este link.

  • Podemos hacer analisis de sentimiento sobre los tweets (link)

  • Referencia estrella: Un buen ejemplo de lo que pueden llegar a construir https://johncoene.shinyapps.io/chirp_demo/

# Twitter app: CausalDaboin 
# API KEY: qRjiyqFdYW5ezrubjxaJrJTIs 
# API KEY SECREt: RrJHuTF9XxL8xfj6JWvsTWQcKwNxKIbEOTcin6COvvvp5UMBKN
# Bearer token: AAAAAAAAAAAAAAAAAAAAALGVegEAAAAA0F1X2qZglMkNDWPeKjffB3dKLdo%3DPUpmJwvjGPBkGoS5Pq4wxNTtJDBzmj6bBXkS11qDwzAIiBum5m 
# Access token secret: M0r9wA2Re0GVfrkFLwBki8iWyQ3IujwEj7IvLIr9zU6yW 
# Access token: 599482460-SKifuC3KQOUb04hMS4JOY1iLCJaEKG9X2ehWwXEx
appname<-"CausalDaboin"
key<-"qRjiyqFdYW5ezrubjxaJrJTIs"
secret<-"RrJHuTF9XxL8xfj6JWvsTWQcKwNxKIbEOTcin6COvvvp5UMBKN"

twitter_token<-create_token(app=appname,
             consumer_key = key,
             consumer_secret = secret,
             access_token = "599482460-SKifuC3KQOUb04hMS4JOY1iLCJaEKG9X2ehWwXEx",
             access_secret = "M0r9wA2Re0GVfrkFLwBki8iWyQ3IujwEj7IvLIr9zU6yW")

twitter_token
## <Token>
## <oauth_endpoint>
##  request:   https://api.twitter.com/oauth/request_token
##  authorize: https://api.twitter.com/oauth/authenticate
##  access:    https://api.twitter.com/oauth/access_token
## <oauth_app> CausalDaboin
##   key:    qRjiyqFdYW5ezrubjxaJrJTIs
##   secret: <hidden>
## <credentials> oauth_token, oauth_token_secret
## ---
load("data/all_twitter_text_data.RData")

Analicemos algunas cuentas de twitter

## Extraígamos los timelines de dos cuentas
# timelines<-get_timeline(user = c("enlaucab","Unimet","usm_vzla"), n=500)
## Revisemos el data.set que nos devuelve
head(timelines)
## # A tibble: 6 x 90
##   user_id  status_id           created_at          screen_name text       source
##   <chr>    <chr>               <dttm>              <chr>       <chr>      <chr> 
## 1 50366308 1545920778394370049 2022-07-10 00:00:10 enlaucab    "José Hum~ Hoots~
## 2 50366308 1545890579183243272 2022-07-09 22:00:09 enlaucab    "En Carac~ Hoots~
## 3 50366308 1545860391888093187 2022-07-09 20:00:12 enlaucab    "Hasta el~ Hoots~
## 4 50366308 1545830200985935873 2022-07-09 18:00:14 enlaucab    "Con gala~ Hoots~
## 5 50366308 1545813681992335360 2022-07-09 16:54:36 enlaucab    "Ya está ~ Twitt~
## 6 50366308 1545800117726461953 2022-07-09 16:00:42 enlaucab    "Ucabista~ Hoots~
## # ... with 84 more variables: display_text_width <dbl>,
## #   reply_to_status_id <chr>, reply_to_user_id <chr>,
## #   reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, quote_count <int>,
## #   reply_count <int>, hashtags <list>, symbols <list>, urls_url <list>,
## #   urls_t.co <list>, urls_expanded_url <list>, media_url <list>,
## #   media_t.co <list>, media_expanded_url <list>, media_type <list>, ...
## vemaos cual tiene mas segudores, cual tiene mas likes, y cual tiene mas retweets
timelines %>% 
  # screen name es el nombre de la cuenta
  group_by(screen_name) %>% 
  summarise(avg_likes=mean(favorite_count,na.rm=T),
            avg_retweets=mean(retweet_count, na.rm=T))
## # A tibble: 3 x 3
##   screen_name avg_likes avg_retweets
##   <chr>           <dbl>        <dbl>
## 1 enlaucab        12.7         5.79 
## 2 Unimet           1.62        0.677
## 3 usm_vzla         4.25        1.18
## Veamos lo mismo, pero a nivel de mes
timelines %>%
  filter(year(created_at)==2022 & month(created_at)>5) %>% 
  group_by(year(created_at),month(created_at),screen_name) %>% 
  summarise(avg_likes=mean(favorite_count,na.rm=T),
            avg_retweets=mean(retweet_count, na.rm=T)) 
## # A tibble: 6 x 5
## # Groups:   year(created_at), month(created_at) [2]
##   `year(created_at)` `month(created_at)` screen_name avg_likes avg_retweets
##                <dbl>               <dbl> <chr>           <dbl>        <dbl>
## 1               2022                   6 enlaucab         9.58        5.43 
## 2               2022                   6 Unimet           1.77        0.752
## 3               2022                   6 usm_vzla        13.7        20.9  
## 4               2022                   7 enlaucab        27.0         7.44 
## 5               2022                   7 Unimet           1.07        0.387
## 6               2022                   7 usm_vzla         5.5         1.58
# Podemos obtener listas de followers con get_followers()
# followers_ucab<-get_followers("enlaucab")
# followers_edn<-get_followers("escueladenada")

## revisemos el listado de followers de la ucab
followers_ucab
## # A tibble: 5,000 x 1
##    user_id            
##    <chr>              
##  1 356500474          
##  2 4203492083         
##  3 1510687080669712387
##  4 1279539269783224321
##  5 717416416350248960 
##  6 1123568213919444993
##  7 1502055476657369114
##  8 364002789          
##  9 1545180746457010181
## 10 2387046542         
## # ... with 4,990 more rows
# Proporcion de seguidores de la UCAB que sige a escuela de nada
nrow(followers_ucab %>% 
  inner_join(followers_edn, by="user_id"))/nrow(followers_ucab)
## [1] 0.0046
## Podemos complementar la informacion de los usuarios con lookup_users()
# followers_ucab_plus<-lookup_users(followers_ucab$user_id, parse = TRUE, token = NULL) 
# followers_edn_plus<-lookup_users(followers_edn$user_id, parse = TRUE, token = NULL) 

## Revisemos el listado de followers aumentado de la UCAB
head(followers_ucab_plus)
## # A tibble: 6 x 90
##   user_id             status_id  created_at          screen_name  text    source
##   <chr>               <chr>      <dttm>              <chr>        <chr>   <chr> 
## 1 356500474           154303509~ 2022-07-02 00:53:28 eudyziliani  "Hermo~ Twitt~
## 2 4203492083          154454347~ 2022-07-06 04:47:14 Holwsito     "@Sm0k~ Twitt~
## 3 1510687080669712387 154593570~ 2022-07-10 00:59:29 laage1965    "Want ~ Twitt~
## 4 1279539269783224321 154593231~ 2022-07-10 00:46:00 pdeportiva8~ "Nada ~ Twitt~
## 5 717416416350248960  154593190~ 2022-07-10 00:44:23 Johy_3029    "Dios ~ Twitt~
## 6 1123568213919444993 154591999~ 2022-07-09 23:57:04 AbgLuciaGar~ "Están~ Twitt~
## # ... with 84 more variables: display_text_width <int>,
## #   reply_to_status_id <chr>, reply_to_user_id <chr>,
## #   reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, quote_count <int>,
## #   reply_count <int>, hashtags <list>, symbols <list>, urls_url <list>,
## #   urls_t.co <list>, urls_expanded_url <list>, media_url <list>,
## #   media_t.co <list>, media_expanded_url <list>, media_type <list>, ...
## Vemaos donde estan ubicados estos seguidores
followers_ucab_plus %>%
  mutate(lugar=case_when(str_detect(location,"aracas")|
                           str_detect(location, "Distrito Capital")~"Caracas",
                         str_detect(location,"zuela") |
                           str_detect(location,"Vzla") |
                           str_detect(location,"VENEZUELA")~"Venezuela",
                         TRUE~location)) %>% 
  group_by(lugar) %>% 
  summarise(count=n(),
            share=count/nrow(followers_edn_plus)*100) %>% 
  top_n(20,count) %>% 
  arrange(desc(count))
## # A tibble: 22 x 3
##    lugar                            count share
##    <chr>                            <int> <dbl>
##  1 ""                                3098 62.0 
##  2 "Venezuela"                        729 14.6 
##  3 "Caracas"                          531 10.6 
##  4 "España"                             8  0.16
##  5 "Miami, FL"                          8  0.16
##  6 "Buenos Aires, Argentina"            7  0.14
##  7 "Bogotá, D.C., Colombia"             6  0.12
##  8 "Lima, Peru"                         6  0.12
##  9 "San Antonio De Los Altos, Vene"     5  0.1 
## 10 "Valencia"                           5  0.1 
## # ... with 12 more rows
## Veamos donde estan ubicados los seguidores de EDN
followers_edn_plus%>%
  mutate(lugar=case_when(str_detect(location,"aracas")|
                           str_detect(location, "Distrito Capital")~"Caracas",
                         str_detect(location,"zuela") |
                           str_detect(location,"Vzla") |
                           str_detect(location,"VENEZUELA")~"Venezuela",
                         TRUE~location)) %>% 
  group_by(lugar) %>% 
  summarise(count=n(),
            share=count/nrow(followers_edn_plus)*100) %>% 
  top_n(20,count) %>% 
  arrange(desc(count))
## # A tibble: 28 x 3
##    lugar                            count share
##    <chr>                            <int> <dbl>
##  1 ""                                3368 67.4 
##  2 "Venezuela"                        618 12.4 
##  3 "Caracas"                          228  4.56
##  4 "Santiago, Chile"                   27  0.54
##  5 "Bogotá, D.C., Colombia"            16  0.32
##  6 "Lima, Peru"                        16  0.32
##  7 "Miami, FL"                         16  0.32
##  8 "Chile"                             15  0.3 
##  9 "Buenos Aires, Argentina"           12  0.24
## 10 "Ciudad Autónoma de Buenos Aire"    12  0.24
## # ... with 18 more rows

Busquémos tweets

# Busquemos tweets con search_tweets
# tweets<-search_tweets("UCAB",n=1000, include_rts = FALSE)
# tipicamente nos interesa conservar el nombre del usuario, el texto, la cantidad de likes y la cantidad de reweets
tweets %>% 
  select(screen_name, text, favorite_count, retweet_count)
## # A tibble: 1,000 x 4
##    screen_name     text                             favorite_count retweet_count
##    <chr>           <chr>                                     <int>         <int>
##  1 The_SoulGreen   "WTF una academia de gamers en ~              0             0
##  2 Jmsn96          "Lo único en lo que nunca se ha~              1             0
##  3 LuisSaidThat    "@sam_nenko @p1edrer0 Reconocer~              0             0
##  4 marquelisgrecia "\U0001f468\U0001f3fb<U+200D>\U0001f393~              0             0
##  5 PolitikaUCAB    "Conoce la oferta académica vig~              0             0
##  6 PolitikaUCAB    "Conoce la oferta académica vig~              0             1
##  7 PolitikaUCAB    "UCAB y @CreemosAlianzaC\ndicen~              1             0
##  8 PolitikaUCAB    "¿Ya visitaste nuestro canal de~              0             0
##  9 PolitikaUCAB    "Te esperamos en nuestro nuevo ~              0             0
## 10 SLuisMiguel     "@arq_guimaraes En la UCAB ense~              0             0
## # ... with 990 more rows

Parece qe los teweets de la UCAB se dispararón el 9 de Julio. ¿Alguién tiene idea de que pasó ese día?

# hay una funcion pre-fabricada para visualizar la cantidad de tweets en el tiempo
ts_plot(data = tweets, by= "hour")

Encontremos las expresiones más comunes

El procesamiento de datos de texto requiere conocimiento sobre RegEx (regular expresions), una sintáxis útil para identificar patrones en un texto determinado. Acá les dejo una guía que me pareció muy buena.

# Preprocesamiento de los tweets

## Primero Limpiemos el texto 
### Remueve caracteres especiales como /, @, | , etc
### Remueve espacios innecesarios
### Pon tood en minusculas
clean_tweets <- function(x) {
    x %>%
        # Remove URLs
        str_remove_all(" ?(f|ht)(tp)(s?)(://)(.*)[.|/](.*)") %>%
        # Remove mentions e.g. "@my_account"
        str_remove_all("@[[:alnum:]_]{4,}")   %>%
        # Remove hashtags
        str_remove_all("#[[:alnum:]_]+") %>% 
       # Remove icons e.g. \U0001f468
        # str_remove_all("[^\x01-\x7F]") %>%
       # Replace "&" character reference with "and"
        str_replace_all("&amp;", "y") %>%
       # Remove puntucation, using a standard character class
       str_remove_all("[[:punct:]]") %>%
       # Remueve otros simbolos como | y /
       str_remove_all("/") %>%
       str_remove_all("\\|") %>%
       # Remove "RT: " from beginning of retweets
       str_remove_all("^RT:? ") %>%
       # Replace any newline characters with a space
       str_replace_all("\\\n", " ") %>%
       # Make everything lowercase
       str_to_lower() %>%
       # Remove any trailing whitespace around the text
       str_trim("both")
}

# Podemos discutir si remover o no quotes y retweets, pero no lo haremos
# text_cleaned<-clean_tweets(subset(tweets,is_retweet==F |is_retweet==F)$text)

text_cleaned<-clean_tweets(tweets$text)
## Introduce los tweets en un "cuerpo" de texto, una estructura de datos para el analiss de texto

clean_corpus<-function(texto=text_cleaned,
                       raiz=TRUE){
  
### Para el corpus debemos almacenar la data en un objeto source. Si la data esta en vector, usa VectorSource(), si esta en data.frame, usa DataframeSource()
ds <- VectorSource(texto)
### usa la funcion simplecorpus, recuerda especificar el idioma
texto_corpus<-SimpleCorpus(x = ds , control = list(language = "sp"))

## Empieza otra proxima ronda de limpieza

### Remueve los stopwords (i,e. el, las, que, un, por, como, si, este, tambien,etc)
texto_corpus <- tm_map(texto_corpus, removeWords, stopwords("spanish"))
### Aca puedes aprovechar de remover otras palabras que quieras sacar
texto_corpus <- tm_map(texto_corpus, removeWords, c("ucab", "universidad", "venezuela","venezolano","saime"))

if (raiz==TRUE){
### Reduce cada palabra a su forma de raiz (root form)
texto_corpus <- tm_map(texto_corpus, stemDocument,language = "spanish")
}

return(texto_corpus)
}

texto_analisis<-clean_corpus(texto = text_cleaned, raiz=FALSE) #Como el texto es bastante basico no quiero reducir las palabras, pero si quieren vean que pasa cuando usamos raiz=TRUE (Corre esto en la consola: wordStem(c("aprender", "aprendiendo", "aprendió"), "spanish"))
# Descomponé el cuerpo de texto en una matriz donde cada celda es una palabra
create_matrix<-function(texto_corpus=texto_analisis){

## Construye una matriz term-document
texto_corpus_matrix <- TermDocumentMatrix(texto_corpus)
tc_m <- as.matrix(texto_corpus_matrix)
print(tc_m[1:5,1:10])

## Crea un data.frame con la frecuencia en la que aparece cada palabra (usaremos formulas de manipulacion de matrices)

### Primero crea un vector con el total de apariciones de cada palabra (suma de filas)
tc_frecuencia<-rowSums(tc_m)

### Luego crea un data.frame
tc_data <- data.frame(word = names(tc_frecuencia),freq=tc_frecuencia) %>% 
  arrange(desc(freq))

return(list(data=tc_data,
            matrix=texto_corpus_matrix))
}



texto_data<-create_matrix(texto_corpus = texto_analisis)[[1]]# Para visualizaciones
##           Docs
## Terms      1 2 3 4 5 6 7 8 9 10
##   academia 1 0 0 1 0 0 0 0 0  0
##   counters 1 0 0 0 0 0 0 0 0  0
##   gamers   1 0 0 1 0 0 0 0 0  0
##   jugadera 1 0 0 0 0 0 0 0 0  0
##   plena    1 0 0 0 0 0 0 0 0  0
texto_matriz<-create_matrix(texto_corpus = texto_analisis)[[2]]# Para outputs mas analiticos
##           Docs
## Terms      1 2 3 4 5 6 7 8 9 10
##   academia 1 0 0 1 0 0 0 0 0  0
##   counters 1 0 0 0 0 0 0 0 0  0
##   gamers   1 0 0 1 0 0 0 0 0  0
##   jugadera 1 0 0 0 0 0 0 0 0  0
##   plena    1 0 0 0 0 0 0 0 0  0
### revisemos la nueva data
head(texto_data, 5)
##                    word freq
## academia       academia  249
## esports         esports  190
## videojuegos videojuegos   56
## deportes       deportes   55
## primarias     primarias   53
inspect(texto_matriz)
## <<TermDocumentMatrix (terms: 3579, documents: 1000)>>
## Non-/sparse entries: 9459/3569541
## Sparsity           : 100%
## Maximal term length: 29
## Weighting          : term frequency (tf)
## Sample             :
##               Docs
## Terms          217 343 559 560 561 562 824 928 929 944
##   academia       0   0   0   0   0   0   0   0   0   0
##   caracas        0   0   1   1   1   1   0   0   0   0
##   centro         0   0   0   0   0   0   0   1   1   0
##   deportes       0   0   0   0   0   0   0   0   0   0
##   electrónicos   0   0   0   0   0   0   0   0   0   0
##   esports        0   0   0   0   0   0   0   0   0   0
##   inauguró       0   0   0   0   0   0   0   0   0   0
##   primarias      0   1   0   0   0   0   0   0   0   0
##   primera        0   0   0   0   0   0   0   0   0   0
##   videojuegos    0   0   0   0   0   0   0   0   0   0
# Visualicemos la data
texto_data %>% 
  top_n(15, freq) %>% 
  ggplot(aes(x= reorder(word,freq), y=freq))+
  geom_col(color="white")+
  coord_flip()+
  labs(title="Palabras mas comunes en tweets que mencionan `UCAB`",
       x="Palabra",
       caption = paste0("Muestra aleatoria de 1000 tweets, publicados en ",
       date(min(tweets$created_at))))

# Generemos una nube de palabras
set.seed(1234)
wordcloud(words = texto_data$word, freq = texto_data$freq, min.freq = 5,
          max.words=100, random.order=FALSE, rot.per=0.40, 
          colors=brewer.pal(8, "Dark2"))

### Hagamoslo con otro tipo de tweets
# tweets_saime<-search_tweets("SAIME",n=1000, include_rts = FALSE)

#### Anidemos las funciones que creamos antes (eso es lo bueno de haberlas hecho ;D )
texto_data_saime<-create_matrix(texto_corpus = 
  clean_corpus(texto = clean_tweets(
    x = tweets_saime$text),
    raiz = FALSE))[[1]]
##             Docs
## Terms        1 2 3 4 5 6 7 8 9 10
##   llega      1 0 0 0 0 0 0 0 0  0
##   tambien    1 0 0 0 0 0 0 0 0  0
##   activistas 0 1 0 0 0 0 0 0 0  0
##   agrupa     0 1 0 0 0 0 0 0 0  0
##   alerta     0 1 0 0 0 0 0 0 0  0
set.seed(1234)
wordcloud(words = texto_data_saime$word,
          freq = texto_data_saime$freq, min.freq = 5,
          max.words=100, random.order=FALSE, rot.per=0.40, 
          colors=brewer.pal(8, "Dark2"))

Asociación de palabras

Hemos usado muchas veces la correlación para entender que tán correlacionadas están distintas variables entre si. Esta técnica también puede usarse para ver que palabras se asocian mas frecuentemente a otras en el texto analizado. Echemos un vestazo a las palabras mas asociadas con las palabras “pasaporte” y “meses”.

matriz_data_saime<-create_matrix(clean_corpus(texto = clean_tweets(x = tweets_saime$text), raiz = FALSE))[[2]]
##             Docs
## Terms        1 2 3 4 5 6 7 8 9 10
##   llega      1 0 0 0 0 0 0 0 0  0
##   tambien    1 0 0 0 0 0 0 0 0  0
##   activistas 0 1 0 0 0 0 0 0 0  0
##   agrupa     0 1 0 0 0 0 0 0 0  0
##   alerta     0 1 0 0 0 0 0 0 0  0
findAssocs(matriz_data_saime, terms = c("pasaporte","meses"), corlimit = .35)   
## $pasaporte
##   agilización      antecede     anulacion       capture       consula 
##          0.39          0.39          0.39          0.39          0.39 
## dactidoscopil       desvios   ecuperación       reseteo           vip 
##          0.39          0.39          0.39          0.39          0.39 
##     solicitud     prórrogas 
##          0.38          0.35 
## 
## $meses
##       aun      $500 adelantar    demora     ponga      dura      mano   trabaje 
##      0.44      0.39      0.39      0.39      0.39      0.37      0.36      0.36 
##   asignan    pongan 
##      0.36      0.35

Introducción a analisis de sentimientos

La última técnica que veremos en la sección tiene que ver con el analisis de sentimientos. Sucintamente, esta técnica se útiliza para extraer información sobre la connotación del lenguaje en un documento. Se usa recurrentemente en el marketing y en la política.

Referencias: https://programminghistorian.org/es/lecciones/analisis-de-sentimientos-r#paquete-syuzhet

# regular sentiment score using get_sentiment() function and method of your choice
# please note that different methods may have different scales

excluir<-c("venezolanos","país","200$","chile","zulianos")

sentimientos_df <- get_nrc_sentiment( subset(texto_data_saime, 
                                             !word %in% excluir)[c(1:900),]$word,
                                      language = "spanish")

# Vistazo a los datos
head(sentimientos_df)
##   anger anticipation disgust fear joy sadness surprise trust negative positive
## 1     0            0       0    0   0       0        0     0        0        0
## 2     0            0       0    0   0       0        0     1        0        0
## 3     0            0       0    0   0       0        0     0        0        0
## 4     0            0       0    0   0       0        0     0        0        0
## 5     0            1       0    0   0       0        1     0        1        1
## 6     0            0       0    0   0       0        0     0        0        0
# Visualicemos la proporcion de palabras asociadas a cada sentimiento

sentimientos_df %>% 
  # Apliquemos pivot longer. Lo recuerdan? es de la primera clase
  tidyr::pivot_longer( cols = everything(),
                       names_to = "emocion",
                      values_to = "valor") %>% 
  group_by(emocion) %>% 
  mutate(share=mean(valor)) %>% 
  ggplot(aes(x=reorder(emocion,share),y=share))+
  geom_col()+
  labs(title="Analisis de sentimiento de tweets que mencionan `SAIME`",
       x="Emocion",
       caption = paste0("Muestra aleatoria de 1000 tweets, publicados en ",
       date(min(tweets_saime$created_at))," previamente tokenizados"))

Networks

El análisis de redes nos vendrá muy bien para comprender las formas en las que viaja la información entre distintos usuarios de twitter.

Usaremos la aplicación Chipr, desarrollada en Shiny, para extraer una base de datos de la Tiwtter API en un formato idóneo para el análisis de redes (Veamos la app antes de ir al codigo)

Creando nuestra primera red

# Previamente descarqué una data llamada Guaidó para que la analizaramos hoy. Si alguno tiene una idea genial podemos hacerlo con una base de datos diferente.
# (la data esta en el data.frame tw, que se cargo cuando abri este archivo mas arirba [all_twitter_text_data.RData])


## Inspecciono el data.frame tw
head(tw) # es una base de tweets commun, fijate que tiene casi la misma cantidad de columnas que la data con los tweets de la ucab
## # A tibble: 6 x 88
##   user_id             status_id  created_at          screen_name  text    source
##   <chr>               <chr>      <dttm>              <chr>        <chr>   <chr> 
## 1 65649158            154597886~ 2022-07-10 03:50:58 SalvatoreR   "#9Jul~ Twitt~
## 2 1051910588          154597883~ 2022-07-10 03:50:51 nancyaiham   "@GDLA~ Twitt~
## 3 114831411           154597881~ 2022-07-10 03:50:48 pluk2008     "Yo no~ Twitt~
## 4 1361760039837523977 154597876~ 2022-07-10 03:50:34 MGonzalez20~ "La \"~ Twitt~
## 5 1361760039837523977 154597832~ 2022-07-10 03:48:49 MGonzalez20~ "Mient~ Twitt~
## 6 1453578848          154597873~ 2022-07-10 03:50:28 FDPHC_SO     "Guaid~ Twitt~
## # ... with 82 more variables: display_text_width <dbl>,
## #   reply_to_status_id <chr>, reply_to_user_id <chr>,
## #   reply_to_screen_name <chr>, is_quote <lgl>, is_retweet <lgl>,
## #   favorite_count <int>, retweet_count <int>, hashtags <list>, symbols <list>,
## #   urls_url <list>, urls_t.co <list>, urls_expanded_url <list>,
## #   media_url <list>, media_t.co <list>, media_expanded_url <list>,
## #   media_type <list>, ext_media_url <list>, ext_media_t.co <list>, ...
## Armemos una red de Retweets
enlaces<-tw %>% 
  filter(is_retweet==T) %>% 
  # la cuenta que hizo el retwwet esta disponible para los retweets,
  # la cuenta que dio el fav, esta disponible para los favs, y asi.
  select(user_id, retweet_user_id,is_retweet) 

## Este paso es critico: Debes armar una base de datos con los nodos. Cuales son los nodos: Todos los id de usuarios que estan en el origen y el destino de los retweets. No pueden faltar nodos que esten en la base de datos de enlaces.
table(unique(enlaces$retweet_user_id) %in% unique(enlaces$retweet_user_id))
## 
## TRUE 
##   78
nodos<-enlaces %>% 
    select(id=user_id) %>% 
    rbind(enlaces %>% 
          select(id=retweet_user_id)  ) %>% 
  distinct(id) %>% 
  # tengo todos los id de los usuarios en la red.
  # ahora debo adjuntarle los atributos individuales que me interesen
  left_join(tw %>% 
              distinct(id=user_id,screen_name, verified))
  

## Esta función del paquete igraph transforma cualquier data que tenga origen y destino en columnas 1 y 2 en un objeto graph.
data_red_rt<-  igraph::graph_from_data_frame(d = enlaces, 
                                             vertices = nodos,
                                             directed = FALSE)

Inspeccionando la estructura de la red

## Inspeccionemos el objeto data_red_rt
data_red_rt
## IGRAPH 723dcdd UN-- 435 429 -- 
## + attr: name (v/c), screen_name (v/c), verified (v/l), is_retweet (e/l)
## + edges from 723dcdd (vertex names):
##  [1] 65649158           --221378668          
##  [2] 114831411          --79287699           
##  [3] 1361760039837523977--637145380          
##  [4] 1361760039837523977--512442219          
##  [5] 1453578848         --114909288          
##  [6] 1439796459201781765--79287699           
##  [7] 1365395059877830659--505731001          
##  [8] 141654491          --114909288          
## + ... omitted several edges
## Manipulacion: Asi se accede a los atributos de los nodos
V(data_red_rt)$screen_name
##   [1] "SalvatoreR"      "pluk2008"        "MGonzalez2021"   "FDPHC_SO"       
##   [5] "GeomilevR"       "elrealengo3"     "__R2R2__"        "MiguelRavelo20" 
##   [9] "JRJSantaella"    "connieestupinan" "CarlosA95769557" "dona_paloma"    
##  [13] "laloduarte60"    "valliente27"     "Thays_maure"     "GarabatoG"      
##  [17] "EdwarsKing"      "ramirofalconm"   "miguelvilchez16" "cootacr3"       
##  [21] "TuiteroNora"     "LuisRobertoBas6" "alfonsove1"      "chavezsyn"      
##  [25] "YanezErvin"      "Luisalfonsobae3" "joluso2010"      "Montaa84515864" 
##  [29] "rosadodeserto62" "PeixotoPita"     "JULIOVNAVARRO"   "nava_jhonattan" 
##  [33] "aaguilarucv"     "edit_luisa"      "MirianEMendoza"  "rafaelignacioLD"
##  [37] "MarioBrea3"      "beavis19755"     "pcar62"          "moreno_alej540" 
##  [41] "yojan_ayala"     "ElDiabl59987144" "FaustoCambio21"  "mairmasolp"     
##  [45] "Elizabeth_lop14" "jkastro18"       "grmariae"        "DDimadise88"    
##  [49] "Yajaira75970111" "Carlosurbanejaw" "lfldefender"     "EdgarBravoBello"
##  [53] "dilcia_tovar"    "Nelsonpvzla1"    "TheHend7"        "froropezaa"     
##  [57] "hernandobarros3" "Kaleno87"        "leonardo_mojica" "carmensa881"    
##  [61] "ilusionvzla"     "WilliamsZaidee"  "SofaSua75333153" "JessicaGilP"    
##  [65] "sylvianorabock"  "adolfocaroc"     "JOSEAguila52"    "papatakisluna"  
##  [69] "oscarbl"         "josecamilohh"    "ADorgChacao"     "jjvaldezbplus"  
##  [73] "loidadevalera"   "Ctoror"          "rivassarai19"    "PepeVzlano"     
##  [77] "zoraida306"      "Jacobo35321994"  "mangareyes"      "padulensis"     
##  [81] "SecFemeADChacao" "FcomunicMichele" "MZarran"         "Kikogerson"     
##  [85] "jorge49987024"   "Carlosa22514170" "SantiagoDickZ"   "CsarGonzlez5"   
##  [89] "edgardhoover"    "TriniReyes16"    "nuez_zaida"      "electricosroya" 
##  [93] "OscarPaezVe"     "veredicto4F"     "JapoSegun"       "zuindaguiller"  
##  [97] "ArlynsFuentes"   "solminberry"     "carlosgermangar" "crismarucha"    
## [101] "DuinVzla"        "ReinaTo86319109" "VocesDeLaGrita"  "KarenGonza2003" 
## [105] "luiscerong"      "castormacarenio" "VilmaFrancesch4" "ElMorroLecheria"
## [109] "SalgadoEulice"   "TERMINE1TOR"     "jyyz1973"        "JorgeGeorge62"  
## [113] "cgla1967"        "EdgarGalarraga7" "peterkingindi"   "deoli10yacsa"   
## [117] "LaPazPrimero20"  "LuzBlancaVzla"   "VicmarBarbie"    "jakousi5000"    
## [121] "Iris_victoria"   "IsaacMDiaz"      "ivanlop33hotmai" "GordonAlejandro"
## [125] "NazarioCanaAnge" "limarcas"        "Chubetoar"       "OscarArmao"     
## [129] "rafaelsalazarc3" "kleorojas"       "giulik3"         "nelsonmaf"      
## [133] "sotoruz"         "Capp866"         "Lucafon22"       "jos66844136"    
## [137] "omega30508286"   "Abogados10Jesus" "GledysGonzlez1"  "donorione"      
## [141] "amilcarrock"     "AceptaloYya"     "bereuban"        "reniercj"       
## [145] "Gusano2999Gusta" "c7d6b14e0f54466" "argenisjdpvzla"  "ALFREDOYSI"     
## [149] "Venezuelalibr35" "SheidaJimenez"   "RenewEuropa"     "GregorioHered12"
## [153] "jamc005"         "Francis32710528" "ren_cohones"     "MustangWagon"   
## [157] "hoswaldosg"      "reclamo2021"     "marcobonilla30"  "55jamp"         
## [161] "gaetanoblandini" "ediponte"        "beamolero"       "13Heledis"      
## [165] "Mon_Barrios"     "cjgoitia"        "Astrid1979kell"  "AurelinaCH"     
## [169] "milenamata1"     "brian70_ve"      "jesusnini"       "NituPerez"      
## [173] "vj13oliveros"    "HERNANSALDANAC"  "oscaraltuve2"    "luiseloi"       
## [177] "Raquetu"         "adamartinezchie" "ervinoliverosv"  "zukyswimwear"   
## [181] "ElcideFlaca1953" "yimilcejb"       "Edo30792703"     "perio_comunic"  
## [185] "BelkysMrquez4"   "justo_thermo"    "Rjcristian18"    "QuevedoQ"       
## [189] "murillocasanova" "PanchoMoncho"    "emmaj_gonzalez"  "jotabelandria"  
## [193] "velizwilliam64"  "GeorgeArtwell"   "naizir_mariana"  "cortesia_sor"   
## [197] "Abuela_Bella_15" "mapyrc"          "Elizabe14960065" "netmanven_ve"   
## [201] "UNIONyVICTORIA"  "alyacar55"       "JhonnyArmas7"    "lfcr81"         
## [205] "mcwated0356"     "yuly_perozo"     "jcajias"         "sekiio"         
## [209] "JorgeAl44544833" "Tinkeringhalo2"  "dra_mouzo"       "idp240664"      
## [213] "lizcanorubioH"   "BelkysTweets"    "scorpio66966"    "vzlarenace1"    
## [217] "VZLACANDELA"     "rivas_anubis"    "gguevarapalma"   "carmenvriv"     
## [221] "RicardoVera1"    "belkisro467"     "CieloCorazon23"  "HctorBello1"    
## [225] "GloriaGonzlez8"  "Darwiniano"      "JimenezMaria48"  "ramiromorabello"
## [229] "JuHeCha"         "adrianaduque71"  "josein123"       "EricOndarroa"   
## [233] "grabor35"        "LiendoLauri"     "reginoestevez"   "Camiloo410"     
## [237] "arduinifm"       "Jose2Rivas"      "carlosl44679940" "catiraeli"      
## [241] "MmVillacis"      "eduardoplacerda" "jacardio"        "jgvelasquezb"   
## [245] "Asciudadania"    "unagalaxianueva" "MerlynMata3"     "AbisaiAridna"   
## [249] "fandango110831"  "Jo_castromax"    "careduarcam"     "marydevies"     
## [253] "EnderGo15845772" "CassinelliR"     "YusmaryAguilar1" "alexand00426843"
## [257] "DEYSIMMUGUERZAB" "AntonioCPerezH2" "elguin_antoima"  "Jorgito49718006"
## [261] "ManzoTairi"      "MariaMorillo6"   "JosRamiroTorre6" "lunalunera1831" 
## [265] "Patriotaccs1"    "ivan18hole"      "ReguloJLepageL"  "susveray28"     
## [269] "Prrincon"        "LopezMarlyst"    "Guayacannnn"     "Alpf27"         
## [273] "P_e_rla"         "Elena_Figue"     "rbarrientos_g"   "juliocesarh17"  
## [277] "weca46"          "PetucioS"        "dorantesa10"     "mangelcc"       
## [281] "yolaven"         "douglasogonzal1" "juandpahe"       "lemancy"        
## [285] "jennyca35561303" "ClementeSeplve1" "Benijesus28"     "ACamposVPValera"
## [289] "mariaemaggi"     "elguillei"       "David1329930754" "pfornera"       
## [293] "jhon13391"       "IngjoacoGONZA"   "Ismary28635282"  "Raovares63"     
## [297] "Samarjos1"       "tibisaycarvaja1" "JaimeLa55351235" "gerardbey"      
## [301] "Frankribi3"      "xmara15"         "MarioxiOjeda"    "GiuseppeBrai"   
## [305] "Ensuret"         "PaulDooling"     "inversioneslv55" "henry23zulia"   
## [309] "rafaelalexis"    "JoseAguilarAg13" "Afortunada3"     "Leni0410"       
## [313] "rodriguezolga71" "Bladimi65148363" "GracielaSkyM"    "Arnoldofernan16"
## [317] "WGuevarapico"    "rjuzcateguip"    "Estheroviedo21"  "es12828070"     
## [321] "gomezpe"         "BryanOrtega_VE"  "ANTONIORRAMIRE2" "Norberto_Mende8"
## [325] "rafaelr77921423" "RicardoASaezGo1" "mserrano07"      "luchamoral"     
## [329] "Richarderojasm"  "Marlon_7612"     "sllucchese"      "EchegarayI"     
## [333] "AraguaTamanaco"  "norma1502"       "enyub"           "Mikelmoro"      
## [337] "RafaelR43545247" "mervinalvarado3" "ADONAYDUQUE"     "robertoriosp"   
## [341] "barazartemirian" "avilancaro"      "IgnacioVillafu5" "necovery"       
## [345] "ifeep"           "A33905926"       "luisorva"        "CarlosLodije"   
## [349] "yimyjo"          "bellarubiavalk"  "gbfenix"         "IsabelC26333765"
## [353] "ZoiloFlores1"    "guerrero0405"    "Vittorioch_"     "Ernesto49690273"
## [357] "mallumiquingas"  "leoncitcandanga" "Orlando57524372" "lenguafree"     
## [361] "DDagobertoGomez" NA                "monicacorrales"  NA               
## [365] NA                NA                NA                NA               
## [369] NA                NA                NA                NA               
## [373] NA                NA                "TIBYPlaza"       NA               
## [377] NA                NA                NA                NA               
## [381] NA                NA                NA                NA               
## [385] NA                NA                NA                NA               
## [389] NA                NA                NA                NA               
## [393] NA                NA                "369Cyrus"        NA               
## [397] NA                "radiocentroec"   "luiscoOficial"   NA               
## [401] NA                NA                NA                NA               
## [405] NA                NA                NA                NA               
## [409] NA                NA                NA                NA               
## [413] NA                NA                NA                NA               
## [417] NA                NA                NA                "richarhernandez"
## [421] NA                "RCR750"          NA                NA               
## [425] NA                "la_patilla"      NA                "contrapuntovzla"
## [429] NA                NA                NA                NA               
## [433] NA                NA                NA
## Manipulacion, asi se accede a los atributos de los enlaces 
E(data_red_rt)$is_retweet
##   [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [16] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [31] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [46] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [61] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [76] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
##  [91] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [106] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [121] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [136] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [151] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [166] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [181] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [196] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [211] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [226] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [241] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [256] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [271] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [286] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [301] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [316] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [331] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [346] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [361] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [376] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [391] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [406] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [421] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## Primer concepto de red: Densidad: Numero de enlaces/n(1-1)
igraph::edge_density(data_red_rt) # la densidad es bastante baja
## [1] 0.004544732

Calculando la importancia de cada nodo: Centralidad

Hay muchisimas medidas de centralidad, acá la mediremos de la forma mas simple: Cuantos retweets están asociados a cada cuenta

## Calculemos la cantidad de retweets de cada usuario: Degree centrality
hist(igraph::degree(data_red_rt))

V(data_red_rt)$centrality<-igraph::degree(data_red_rt)

# igraph::betweenness(): Te dice el numero de "caminos mas cortos" en los que participa un nodo. Alto betweeness, alta capacidad de conectar a distintas personas. 
# igraph::closeness(): Te dice el inverso de la cantidad de pasos promedio al resto de la red. Si en promedio llegas a cualquer nodo en 5 pasos, closeness sera 0.2. Si en promedio te tomas 10 pasos, closeness sera 0.1

Apliquemos un algoritmo de deteccion de comunidades

## Definamos la comunidad o cluster al que pertenece cada nodo
## Las comunidades son grupos cuya interaccion interna excede a la interaccion externa 
community<-igraph::cluster_louvain(data_red_rt)

## Asignamos cada nodo a su comunidad, aca nos aseguramos de que se pueda ver en la red
V(data_red_rt)$community<-community$membership

Visualizando la red

## El paquete ggraph es el mas conveniente y versatil para visualizar redes (en mi opinion). Fijate que funciona igual, con la salvedad de que los geoms estan distinguidos para nodes y enlaces

ggraph::ggraph(data_red_rt)+
  ggraph::geom_node_point(aes(size=centrality, fill=as.factor(community)),
                          shape=21)+
  ggraph::geom_edge_link()+
  # sabemos que hay demasiadas comunidades, no pongamos la leyenda en este caso
  theme(legend.position = "none")

Grafiquemos la version final

### Puedo excluir a las comunidades pequenas de forma muy similar a como lo hjaria con un data.frame normal.

sort(table(V(data_red_rt)$community))
## 
##  8  9 10 17 18 21 25 26 29 30 31 33 36 39 40 41 42 43 44 45 46 47  1  7 13 20 
##  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  3  3  3  3 
## 22 24 32 12 19 27 34 37 14 16 11 23  6  4  5 35  2 38  3 28 15 
##  3  3  3  4  4  4  4  5  7  8  9 13 14 16 16 18 35 35 47 50 81
big_communities<-c(12,28,3,38,2,35)

data_red_rt_small<-delete_vertices(data_red_rt, ! V(data_red_rt)$community %in% big_communities)

ggraph::ggraph(data_red_rt_small)+
  ggraph::geom_edge_link()+
  ggraph::geom_node_point(aes(size=centrality, fill=as.factor(community)),
                          shape=21)+
  # podemos agregarle texto 
  ggraph::geom_node_text(aes(filter = centrality>1, label = screen_name ),
                         family = "serif") +
  # ampliamos el rango de variacion del tamano
  # scale_size(range = c(1, 8)) +
  # le ponemos fondo blanco
  theme_graph() +
  # sabemos que hay demasiadas comunidades, no pongamos la leyenda en este caso
  theme(legend.position = "none")

Referencias